#ifndef     __PROTO_HANDLER_H
#define     __PROTO_HANDLER_H

#include "./proto.h"
#include "../utils/alg.h"
#include "../utils/byte_view.h"
#include <string.h>
#include "../driver/cmd_uart.h"

namespace conn {

template <typename EXEC, uint32_t CACHE_BUF_SIZE>
class ProtoHandler {

public:
    void exec() {

        for (;;) {
            poll();
            for (; !is_empty() ;) {
                exec_once();
            }
        }
    }

private:
    inline void poll() {
        static_cast<EXEC *>(this)->poll(m_bw);
    }

    inline bool wait_next() {
        return static_cast<EXEC *>(this)->poll(m_bw, 100);
    }

    inline void ready(const uint8_t *buf, uint8_t len) {
        return static_cast<EXEC *>(this)->proto_ready(buf, len);
    }

    void exec_once() {
        using namespace driver;

        if (!sync_head()) {
            cmdUart->log("sync fail");
            return;
        }

        if (is_empty() && (!wait_next())) {
            cmdUart->log("not recv len");
            return;
        }

        uint8_t len = take_if_not_empty();
        if (len < 7) {
            cmdUart->log("len < 7: %d", len);
            return;
        }

        len -= 3;
        if ((m_cache_index + len) > CACHE_BUF_SIZE) {
            m_cache_index = 0;
        }
        uint8_t *buf = m_cache_buf + m_cache_index;
        uint8_t index = 0;

        for (; index < len; ) {
            if (is_empty() && (!wait_next())) {
                cmdUart->log("recv fail");
                return;
            }

            uint8_t n = take(buf + index, len - index);
            index += n;
        }
        
        uint8_t s = utils::alg::sum_xor(buf, len - 2);
        if (s != buf[len - 2]) {
            cmdUart->log("xor sum fail");
            return;
        }

        if (frame::END != buf[len - 1]) {
            cmdUart->log("end fail");
            return;
        }

        m_cache_index += index;
        ready(buf, index);
    }

    bool sync_head() {
        bool flag = false;
        for (;;) {
            if (is_empty() && (!wait_next())) {
                return false;
            }

            uint8_t v = take_if_not_empty();
            if (flag && (v == frame::HEAD1)) {
                return true;
            }
            flag = v == frame::HEAD0;
        }
    }

    inline bool is_empty() {
        return m_bw.len == 0;
    }

    uint8_t take_if_not_empty() {
        uint8_t v = m_bw.buf[0];
        m_bw.buf ++;
        m_bw.len --;
        return v;
    }

    uint8_t take(uint8_t *dest, uint8_t len) {
        if (len > m_bw.len) {
            len = m_bw.len;
        }
        memcpy(dest, m_bw.buf, len);
        m_bw.buf += len;
        m_bw.len -= len;
        return len;
    }

    uint8_t m_cache_buf[CACHE_BUF_SIZE];
    uint32_t m_cache_index = 0;
    utils::ByteView m_bw;
};

}


#endif
